﻿using EBA.Contracts;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.Text;

namespace EBA.IoC
{
    public class SimpleContainer : IContainer
    {


        #region Constructor
  
        public SimpleContainer(AutowiredConventions conventions)
        {
            Contract.Requires<ArgumentNullException>(conventions != null, "conventions");

            this.Conventions = conventions;

        }
        #endregion

        public AutowiredConventions Conventions
        {
            get;
            set;
        }

        #region LifetimeManager
  
        public ILifetimeManager LifetimeManager { get; set; }

        #endregion

        #region Get

        public TExport GetExport<TExport>(ILifetimeManager lifetimeManager = null)
        {
            return (TExport)this.GetExport(typeof(TExport), lifetimeManager);
        }

        public TExport GetExport<TExport>(string contractName, ILifetimeManager lifetimeManager = null)
        {
            return (TExport)this.GetExport(typeof(TExport), contractName, lifetimeManager);
        }

        public object GetExport(Type exportType, ILifetimeManager lifetimeManager = null)
        {
            var convention = this.Conventions.Exports[exportType];

            if (convention == null)
            {
                return this.GetOrCreateInstance(exportType, lifetimeManager);
            }
            else
            {
                var componentType = convention.Where(i => i.ContractType == exportType && i.ContractName == null).Select(i => i.ComponentType).FirstOrDefault();

                if (componentType == null)
                {
                    return this.GetOrCreateInstance(exportType, lifetimeManager);
                }
                else
                {
                    return this.GetOrCreateInstance(componentType, lifetimeManager);
                }
            }
        }

        public object GetExport(Type exportType, string contractName, ILifetimeManager lifetimeManager = null)
        {
            var convention = this.Conventions.Exports[exportType];

            if (convention == null)
            {
                return this.GetOrCreateInstance(exportType, lifetimeManager);
            }
            else
            {
                var componentType = convention.Where(i => i.ContractType == exportType && i.ContractName == contractName).Select(i => i.ComponentType).FirstOrDefault();

                if (componentType == null)
                {
                    return this.GetOrCreateInstance(exportType, lifetimeManager);
                }
                else
                {
                    return this.GetOrCreateInstance(componentType, lifetimeManager);
                }
            }
        }

        public IEnumerable<TExport> GetExports<TExport>(ILifetimeManager lifetimeManager = null)
        {
            return this.GetExports(typeof(TExport)).OfType<TExport>();
        }

        public IEnumerable<TExport> GetExports<TExport>(string contractName, ILifetimeManager lifetimeManager = null)
        {
            return this.GetExports(typeof(TExport), contractName).OfType<TExport>();
        }

        public IEnumerable<object> GetExports(Type exportType, ILifetimeManager lifetimeManager = null)
        {
            var convention = this.Conventions.Exports[exportType];

            var exports = convention ?? Enumerable.Empty<ExportConvention>();

            var types = exports.Where(i => i.ContractType == exportType && i.ContractName == null).Select(i => i.ComponentType);

            foreach (var type in types)
            {
                yield return this.GetOrCreateInstance(type, lifetimeManager);
            }

        }

        public IEnumerable<object> GetExports(Type exportType, string contractName, ILifetimeManager lifetimeManager = null)
        {
            var convention = this.Conventions.Exports[exportType];

            var exports = convention ?? Enumerable.Empty<ExportConvention>();

            var types = exports.Where(i => i.ContractType == exportType && i.ContractName == contractName).Select(i => i.ComponentType);

            foreach (var type in types)
            {
                yield return this.GetOrCreateInstance(type, lifetimeManager);
            }
        }

        #endregion

        #region TryGet
        public bool TryGetExport<TExport>(out TExport export, ILifetimeManager lifetimeManager = null)
        {
            try
            {
                export = this.GetExport<TExport>();
                return true;
            }
            catch
            {
                export = default(TExport);
                return false;
            }

        }

        public bool TryGetExport<TExport>(string contractName, out TExport export, ILifetimeManager lifetimeManager = null)
        {
            try
            {
                export = this.GetExport<TExport>(contractName);
                return true;
            }
            catch
            {
                export = default(TExport);
                return false;
            }
        }

        public bool TryGetExport(Type exportType, out object export, ILifetimeManager lifetimeManager = null)
        {
            try
            {
                export = this.GetExport(exportType);
                return true;
            }
            catch
            {
                export = null;
                return false;
            }
        }

        public bool TryGetExport(Type exportType, string contractName, out object export, ILifetimeManager lifetimeManager = null)
        {
            try
            {
                export = this.GetExport(exportType, contractName);
                return true;
            }
            catch
            {
                export = null;
                return false;
            }
        }

        #endregion

        #region BuildUp

        public void BuildUp<TEntity>(TEntity instance, ILifetimeManager lifetimeManager = null)
        {
            this.BuildUpInstance(instance, typeof(TEntity), lifetimeManager);
        }

        #endregion

        #region internal methods

        protected virtual object GetOrCreateInstance(Type type, ILifetimeManager lifetimeManager)
        {
            //1. Get it from lieftime first
            object instance = this.TryGetLiveInstance(lifetimeManager, null, type);

            if (instance != null)
            {
                return instance;
            }

            //2. find injections
            var convention = this.Conventions.Imports[type];

            if (convention == null)
            {
                return this.CreateInstance(type);
            }
            else
            {
                if (convention.Constructor != null)
                {
                    var arguments = new List<object>();

                    foreach (var parameter in convention.Constructor.Parameters)
                    {
                        var contractType = parameter.Value.ContractType;
                        var contractName = parameter.Value.ContractName;

                        

                        var argument = this.TryGetLiveInstance(lifetimeManager, type, contractType, contractName);

                        if (argument != null)
                        {
                            arguments.Add(argument);
                        }
                        else
                        {
                            if (parameter.Value.IsCollection)
                            {
                                var values = this.GetExports(contractType, contractName);
                                arguments.Add(ConvertToCollection(parameter.Value.Parameter.ParameterType, contractType, values));
                            }
                            else
                            {
                                arguments.Add(this.GetExport(contractType, contractName));
                            }
                        }
                    }

                    instance = convention.Constructor.Constructor.Invoke(arguments.ToArray());
                }

                else
                {
                    instance = this.CreateInstance(type);
                }

                this.BuildUpInstance(instance, type, lifetimeManager);


                return instance;

            }
        }

        protected virtual object CreateInstance(Type type)
        {
            if (type.IsAbstract)// && type.IsSealed)//static class
            {
                throw new InvalidOperationException(string.Format("Abstract and static class [{0}] do not support to create instance.",type));
            }
            else
            {
                return Activator.CreateInstance(type);
            }
        }


        protected virtual object TryGetLiveInstance(ILifetimeManager lifetimeManager, Type hostType, Type instanceType, string contractName = null)
        {
            var lifetime = lifetimeManager ?? this.LifetimeManager;
            if (lifetime != null)
            {
                if (string.IsNullOrWhiteSpace(contractName))
                {
                    return lifetime.GetInstance(hostType, instanceType);
                }
                else
                {
                    return lifetime.GetInstance(hostType, instanceType, contractName);
                }
            }
            else
            {
                return null;
            }
        }

        protected virtual object ConvertToCollection(Type parameterType, Type elementType, IEnumerable<object> values, ILifetimeManager lifetimeManager = null)
        {
            if (parameterType.IsArray)
            {
                return values.Select(i => Convert.ChangeType(i, elementType)).ToArray();
            }
            else
            {
                if (parameterType.IsGenericType)
                {
                    if (typeof(List<>).IsAssignableFrom(parameterType))
                    {
                        var t = typeof(List<>);
                        var constructedListType = t.MakeGenericType(elementType);
                        var list = (IList)this.CreateInstance(constructedListType);

                        foreach (var value in values)
                        {
                            list.Add(value);
                        }

                        return list;
                    }
                    else
                    {
                        if (typeof(IEnumerable<>).IsAssignableFrom(parameterType))
                        {
                            return values.Select(i => Convert.ChangeType(i, elementType)).AsEnumerable();
                        }
                        else
                        {
                            throw new NotSupportedException(parameterType.ToString());
                        }
                    }
                }
                else
                {
                    throw new NotSupportedException(parameterType.ToString());
                }
            }

        }

        protected virtual void BuildUpInstance(object instance, Type type, ILifetimeManager lifetimeManager)
        {
            if (instance != null)
            {
                var conventions = this.Conventions.Imports[type];

                if (conventions != null)
                {
                    //invoke setter injection
                    foreach (var memberConvention in conventions.Members)
                    {
                        object value = this.TryGetLiveInstance(lifetimeManager, type, memberConvention.ContractType, memberConvention.ContractName);
                        if (value == null)
                        {

                            if (memberConvention.IsCollection)
                            {
                                value = ConvertToCollection(memberConvention.Member.ReflectedType, memberConvention.ContractType, this.GetExports(memberConvention.ContractType, memberConvention.ContractName));
                            }
                            else
                            {
                                value = this.GetExport(memberConvention.ContractType, memberConvention.ContractName);
                            }
                        }

                        switch (memberConvention.Member.MemberType)
                        {
                            case MemberTypes.Field:
                                ((FieldInfo)memberConvention.Member).SetValue(instance, value);
                                break;
                            case MemberTypes.Property:
                                ((PropertyInfo)memberConvention.Member).SetValue(instance, value, null);
                                break;
                            default:
                                throw new ArgumentException("MemberInfo must be if type FieldInfo or PropertyInfo", "member");
                        }

                    }
                }
            }
        }

        #endregion



        public IEnumerable<TExport> GetAllExports<TExport>(ILifetimeManager lifetimeManager = null)
        {
            return this.GetAllExports(typeof(TExport), lifetimeManager).Cast<TExport>();
        }

        public IEnumerable<object> GetAllExports(Type exportType, ILifetimeManager lifetimeManager = null)
        {
            var convention = this.Conventions.Exports[exportType];

            var exports = convention ?? Enumerable.Empty<ExportConvention>();

            var types = exports.Where(i => i.ContractType == exportType).Select(i => i.ComponentType);

            foreach (var type in types)
            {
                yield return this.GetOrCreateInstance(type, lifetimeManager);
            }
        }
    }
}
